#include <cassert>
#include <cstring>

#include "Allocator.hpp"


#ifndef ALIGN
#define ALIGN(x, a)     (((x) + ((a) - 1)) & ~((a) - 1))
#endif

using namespace My;


My::Allocator::Allocator()
    : m_pPageList(nullptr), m_pFreeList(nullptr),
    m_szDataSize(0), m_szPageSize(0), 
    m_szAlignmentSize(0), m_szBlockSize(0), m_nBlocksPerPage(0)
{
}

My::Allocator::Allocator(size_t data_size, size_t page_size, size_t alignment)
    : m_pPageList(nullptr), m_pFreeList(nullptr)
{
    Rest(data_size, page_size, alignment);
}

My::Allocator::~Allocator()
{
}

void My::Allocator::Rest(size_t data_size, size_t page_size, size_t alignment)
{
    FreeAll();

    m_szDataSize = data_size;
    m_szPageSize = page_size;
    
    size_t minimal_size 
        = (sizeof(BlockHeader) > data_size) ? sizeof(BlockHeader) : data_size;

#if defined(_DEBUG)
    // 确保对齐大于0，且为2的幂
    assert(alignment > 0 && ((alignment & (alignment - 1))) == 0);
#endif

    m_szBlockSize = ALIGN(minimal_size, alignment);
    m_szAlignmentSize = m_szBlockSize - minimal_size;
    m_nBlocksPerPage = (m_szPageSize - sizeof(PageHeader)) / m_szBlockSize;
}

void* My::Allocator::Allocate()
{
    if(!m_pFreeList)
    {
        // 无空闲块时，需要新分配内存页
        PageHeader* pNewPage = reinterpret_cast<PageHeader*>(new uint8_t[m_szPageSize]);
        ++m_nPages;
        m_nBlocks += m_nBlocksPerPage;
        m_nFreeBlocks += m_nBlocksPerPage;

#if defined(_DEBUG)
        FillFreePage(pNewPage);
#endif

        // 将上一个旧的内存页连接到新的内存页上，并更新
        if(m_pPageList) pNewPage->pNext = m_pPageList;
        m_pPageList = pNewPage;

        // 将页中的所有块连接在一起
        BlockHeader* pBlock = pNewPage->Blocks();
        for(uint32_t i = 0; i < m_nBlocksPerPage; i++)
        {
            pBlock->pNext = NextBlock(pBlock);
            pBlock = NextBlock(pBlock);
        }
        pBlock->pNext = nullptr;

        // 得到最新分配的空闲块地址
        m_pFreeList = pNewPage->Blocks();
    }

    // 在将最新的空闲块地址提供出去之前，需要将索引关系移到下一块地址中
    BlockHeader* freeBlock = m_pFreeList;
    m_pFreeList = m_pFreeList->pNext;
    --m_nFreeBlocks;

#if defined(_DEBUG)
    FillAllocatedBlock(freeBlock);
#endif

    return reinterpret_cast<void*>(freeBlock);
}

void My::Allocator::Free(void* p)
{
    BlockHeader* block = reinterpret_cast<BlockHeader*>(p);

#if defined(_DEBUG)
    FillFreeBlock(block);
#endif

    // 直接把m_pFreeList绑在block的下一个地址，连接关系会被打乱
    block->pNext = m_pFreeList;
    m_pFreeList = block;
    ++m_nFreeBlocks;

    // 没有涉及到把整个Page都free后要怎么处理，是将整个Page都释放？还是一直保留着？
    // ... ...
}

void My::Allocator::FreeAll()
{
    PageHeader* pPage = m_pPageList;
    while(pPage)
    {
        PageHeader* _p = pPage;
        pPage = pPage->pNext;

        delete[] reinterpret_cast<uint8_t*>(_p);
    }

    m_pPageList = nullptr;
    m_pFreeList = nullptr;

    m_nPages = 0;
    m_nBlocks = 0;
    m_nFreeBlocks = 0;
}

BlockHeader* My::Allocator::NextBlock(BlockHeader *pBlock)
{
    return reinterpret_cast<BlockHeader *>(reinterpret_cast<uint8_t*>(pBlock) + m_szBlockSize);
}

#if defined(_DEBUG)
void My::Allocator::FillFreePage(PageHeader *pPage)
{
    // page header
    pPage->pNext = nullptr;
 
    // blocks
    BlockHeader *pBlock = pPage->Blocks();
    for (uint32_t i = 0; i < m_nBlocksPerPage; i++)
    {
        FillFreeBlock(pBlock);
        pBlock = NextBlock(pBlock);
    }
}

void My::Allocator::FillFreeBlock(BlockHeader *pBlock)
{
    // 用PATTERN_FREE填充
    std::memset(pBlock, PATTERN_FREE, m_szBlockSize - m_szAlignmentSize);

    // 用填充PATTERN_ALIGN填充
    std::memset(reinterpret_cast<uint8_t*>(pBlock) + m_szBlockSize - m_szAlignmentSize,
                PATTERN_ALIGN, m_szAlignmentSize);
}

void My::Allocator::FillAllocatedBlock(BlockHeader *pBlock)
{
    // 用PATTERN_ALLOC填充
    std::memset(pBlock, PATTERN_ALLOC, m_szBlockSize - m_szAlignmentSize);

    // 用填充PATTERN_ALIGN填充
    std::memset(reinterpret_cast<uint8_t*>(pBlock) + m_szBlockSize - m_szAlignmentSize,
                PATTERN_ALIGN, m_szAlignmentSize);
}
#endif